package gameoflive;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Game implements Runnable
{
    public final int    WIDTH  = 1500;
    public final int    HEIGHT =  800;
    
    private static Game instance;
    
    public synchronized static Game getInstance()
    {
        if (instance == null)
        {
            instance = new Game();
        }
        return instance;
    }
    
    private JFrame                            frame;
    private Canvas                            canvas;
    private BufferStrategy                    bufferStrategy;
    
    public static synchronized void log(Object message, boolean newLine)
    {
        System.out.print(message.toString());
        if (newLine)
        {
            System.out.println();
        }
    }
    
    public static synchronized void log(Object message)
    {
        log(message, true);
    }
    
    protected Game()
    {
        frame = new JFrame("Basic Game");
        
        JPanel panel = (JPanel) frame.getContentPane();
        panel.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        panel.setLayout(null);
        
        canvas = new Canvas();
        canvas.setBounds(0, 0, WIDTH, HEIGHT);
        canvas.setIgnoreRepaint(true);
        panel.add(canvas);
        
        canvas.addKeyListener(new KeyboardControl());
        
        canvas.addMouseListener(new MouseControl());
        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setResizable(false);
        frame.setVisible(true);
        
        canvas.createBufferStrategy(2);
        bufferStrategy = canvas.getBufferStrategy();
        
        canvas.requestFocus();
    }
    
    public void setTitle(String title)
    {
        frame.setTitle(title);
    }
    
    protected static boolean IGNORE_MOUSE = false;
    
    private class MouseControl extends MouseAdapter
    {
        @Override
        public void mouseClicked(MouseEvent e)
        {
            if (IGNORE_MOUSE == false)
            {
                IGNORE_MOUSE = true;
                java.awt.Point p = e.getPoint();
                int x = (int) (p.getX() / cellSize);
                int y = (int) (p.getY() / cellSize);
                Cell cell = GameOfLive.getInstance().getCell(x, y);
                cell.setSurviving(true);
                IGNORE_MOUSE = false;
            }
            
        }
    }
    
    private int  stepsFPS   =  5;
    private long desiredFPS = 20;
    private long desiredDeltaLoop;
    
    private void recalcDesiredDeltaLoop()
    {
        desiredDeltaLoop = (1000 * 1000 * 1000) / desiredFPS;
    }
    
    protected static boolean IGNORE_KEYBOARD       = false;
    
    private class KeyboardControl extends KeyAdapter
    {
        @Override
        public void keyPressed(KeyEvent e)
        {
            if (IGNORE_KEYBOARD)
            {
                e.consume();
            }
            else
            {
                IGNORE_KEYBOARD = true;
                final GameOfLive gol = GameOfLive.getInstance();
                final Console console = Console.getInstance();
                if (console.consoleKeyPressed(e.getKeyCode()))
                {
                    if (console.isOpen())
                    {
                        console.closeConsole();
                    }
                    else
                    {
                        console.openConsole();
                    }
                    log("CONSOLE OPEN: " + console.isOpen());
                }
                if (console.isOpen())
                {
                    console.fireKeyEvent(e);
                }
                else
                {
                    short cellSize = 0;
                    switch (e.getKeyCode())
                    {
                        case KeyEvent.VK_MINUS:
                        {
                            if (desiredFPS == 0 || desiredFPS - stepsFPS < 1)
                            {
                                desiredFPS = 1;
                            }
                            else
                            {
                                desiredFPS -= stepsFPS;
                            }
                            log("-FPS: " + desiredFPS);
                            recalcDesiredDeltaLoop();
                            break;
                        }
                        case KeyEvent.VK_PLUS:
                        {
                            desiredFPS += stepsFPS;
                            log("+FPS: " + desiredFPS);
                            recalcDesiredDeltaLoop();
                            break;
                        }
                        case KeyEvent.VK_P:
                        {
                            if (gol.isRunning())
                            {
                                if (gol.isPaused())
                                {
                                    gol.resume();
                                }
                                else
                                {
                                    gol.pause();
                                }
                            }
                            break;
                        }
                        case KeyEvent.VK_R:
                        {
                            gol.restart();
                            break;
                        }
                        case KeyEvent.VK_0:
                        {
                            cellSize = 30;
                            break;
                        }
                        case KeyEvent.VK_1:
                        {
                            cellSize = 20;
                            break;
                        }
                        case KeyEvent.VK_2:
                        {
                            cellSize = 10;
                            break;
                        }
                        case KeyEvent.VK_3:
                        {
                            cellSize = 8;
                            break;
                        }
                        case KeyEvent.VK_4:
                        {
                            cellSize = 6;
                            break;
                        }
                        case KeyEvent.VK_5:
                        {
                            cellSize = 4;
                            break;
                        }
                    }
                    if (cellSize > 0 && cellSize != Game.this.cellSize)
                    {
                        if (gol.isRunning())
                        {
                            gol.stop();
                            log("cellSize: " + (Game.this.cellSize = cellSize));
                            Point size = calcSize();
                            gol.initGeneration(size.getWidth(), size.getHeight());
                        }
                    }
                }
                IGNORE_KEYBOARD = false;
            }
        }
    }
    
    private boolean running = true;
    
    public void run()
    {
        recalcDesiredDeltaLoop();
        Point size = calcSize();
        GameOfLive.getInstance().initGeneration((int)size.getWidth(), (int)size.getHeight());
        while (running)
        {
            try
            {
                long beginLoopTime = System.nanoTime();
                render();
                update();
                long endLoopTime = System.nanoTime();
                long deltaLoop = endLoopTime - beginLoopTime;
                if (deltaLoop <= desiredDeltaLoop)
                {
                    try
                    {
                        Thread.sleep((desiredDeltaLoop - deltaLoop) / (1000 * 1000));
                    }
                    catch (InterruptedException e)
                    {
                        // Do nothing
                    }
                }
            }
            catch (Exception ex)
            {
                StringWriter sw = new StringWriter();
                ex.printStackTrace(new PrintWriter(sw));
                log(sw.toString());
                running = false;
            }
        }
    }
    
    private void render()
    {
        Graphics2D g = (Graphics2D) bufferStrategy.getDrawGraphics();
        render(g);
        g.dispose();
        bufferStrategy.show();
    }
    
    private short cellSize = 8;
    
    private Point calcSize()
    {
        int width = WIDTH;
        if (WIDTH % cellSize != 0)
        {
            width -= WIDTH % cellSize;
        }
        width /= cellSize;
        int height = HEIGHT;
        if (HEIGHT % cellSize != 0)
        {
            height -= HEIGHT % cellSize;
        }
        height /= cellSize;
        return new Point(width, height);
    }
    
    /**
     * Rewrite this method for your game
     */
    protected void update()
    {
        GameOfLive gol = GameOfLive.getInstance();
        if (gol.isRunning() && gol.isPaused() == false)
        {
            if (gol.prepareNextGeneration() == false)
            {
                gol.restart();
            }
        }
        setTitle(gol.getStatus());
    }
    
    protected void clearScreen(Graphics2D g, int width, int height)
    {
        if (width > WIDTH)
        {
            width = WIDTH;
        }
        if (height > HEIGHT)
        {
            height = HEIGHT;
        }
        Color color = g.getColor();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);
        g.setColor(color);
    }
    
    /**
     * Rewrite this method for your game
     */
    protected void render(Graphics2D g)
    {
        GameOfLive gol = GameOfLive.getInstance();
        gol.render(g, cellSize);
        Console con = Console.getInstance();
        if (con.isOpen())
        {
            con.render(g, cellSize);
        }
    }
    
    protected void renderCenterText(Graphics2D g, String s, int fontSize)
    {
        g.setColor(Color.BLACK);
        g.setFont(new Font("Monospaced", Font.PLAIN, fontSize));
        int w2 = g.getFontMetrics().stringWidth(s) / 2;
        int h2 = g.getFontMetrics().getDescent();
        g.drawString(s, WIDTH / 2 - w2, HEIGHT / 2 + h2);
    }
    
    public static void main(String[] args)
    {
         Thread t = new Thread(Game.getInstance());
         t.start();
    }
    
}
